package com.company.nuwa.redis.support.lock;

import com.company.nuwa.common.exception.BizException;
import com.company.nuwa.redis.utils.SpelExpressionUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.util.concurrent.Callable;

/**
 * <p>Redis Locker Aspect</p >
 *
 * @author Nikola Tesla
 * @version 1.0
 * @date 2021/1/12 14:04
 */
@Aspect
@Slf4j
public class RedisLockerAspect {

    public RedisLockerAspect(RedisLocker redisLocker) {
        this.redisLocker = redisLocker;
    }

    private final RedisLocker redisLocker;

    @Pointcut("@annotation(com.company.nuwa.redis.support.lock.RedisLock)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        RedisLock redisLock = methodSignature.getMethod().getDeclaredAnnotation(RedisLock.class);

        String key = SpelExpressionUtil.parseExpression(joinPoint, redisLock.key());

        log.info("signature={} distribute lock key={}, timeout={} ", joinPoint.getSignature().toString(), key, redisLock.timeout());

        Callable<Object> callable = () -> {
            try {
                return joinPoint.proceed(joinPoint.getArgs());
            } catch (Throwable throwable) {
                if (throwable instanceof BizException) {
                    throw (BizException) throwable;
                }
                log.error("", throwable);
                throw new BizException(-1, "服务器打瞌睡了～");
            }
        };
        try {
            return redisLocker.tryLock(key, redisLock.timeout(), redisLock.timeUnit(), callable);
        } catch (Exception e) {
            log.error(String.format("分布式锁异常，服务降级，直接调用方法。 %s", joinPoint.getSignature().toString()), e);
            return callable.call();
        }
    }

}
